/*
 * A class that handles messages between clients and server.
 * the "ans" version of eaxh command is the server side.
 * Each calles method will communicate with the "other side", 
 * and will write data to the connection.
 */

#include "messagehandler.h"
using namespace std;
using namespace client_server;
using namespace protocol;


MessageHandler::MessageHandler(){}
MessageHandler::~MessageHandler(){}
			
void MessageHandler::ans_list_groups(Connection *conn, vector<pair<unsigned int, string> > v) 
	throw(ConnectionClosedException, ProtocolException){
	//COM_LIST_NG already taken care of, verify COM_END now
	char rec = read_code(conn); //reads the servers ans. Expects ANS_LIST_NG
	check_code("COM_END after COM_LIST_NG", p.COM_END, rec); //verifies the servers answer.
	
	//will send the following:
	//	ANS_LIST_NG num_p [num_p string_p]* ANS_END
	write_code(conn, p.ANS_LIST_NG);
	
	size_t size = v.size();
	write_uint(conn, size); // num_p
	
	for(vector<pair<unsigned int, string> >::iterator it = v.begin(); it != v.end(); it++) {
		write_uint(conn, it->first);
		write_string(conn, it->second);
		//Writes [num_p string_p]*
	}
	
	write_code(conn, p.ANS_END);
}

vector<pair<unsigned int, string> > MessageHandler::list_groups(Connection *conn) 
		throw(ConnectionClosedException, ProtocolException){
		
	vector<pair<unsigned int, string> > v;
	write_code(conn, p.COM_LIST_NG);
	write_code(conn, p.COM_END);
	//Writes COM_LIST_NG COM_END
	//Command is now sent to server.
	//start reading ans.
	//Expects ANS_LIST_NG num_p [num_p string_p]* ANS_END
	
	char rec = read_code(conn); //reads the servers ans. Expects ANS_LIST_NG
	check_code("List all groups", p.ANS_LIST_NG, rec); //verifies the servers answer.
	
	unsigned int no_of_groups = read_uint(conn); //Expects (and verifies) num_p

	for(int i = 0; i < no_of_groups; i++){
		//expects [num_p string_p]
		unsigned int p = read_uint(conn);
		string s = read_string(conn); //Expects and verifies string
		v.push_back(make_pair<unsigned int, string>(p, s));
	}
	
	check_code("Answer should be terminated when listing groups", p.ANS_END, read_code(conn)); //last code in command.
	
	return v;
}
			
void MessageHandler::ans_create_group(Connection *conn, bool created)
	throw(ConnectionClosedException, ProtocolException){
	//COM_CREATE_NG string_p alreade read, only COM_ANS left.
	char rec = read_code(conn);
	check_code("create_group should be COM_END terminated", p.COM_END, rec);
	
	// ANS_CREATE_NG [ANS_ACK | ANS_NAK ERR_NG_ALREADY_EXISTS] ANS_END
	write_code(conn, p.ANS_CREATE_NG);
	if(created)
		write_code(conn, p.ANS_ACK);
	else {
		write_code(conn, p.ANS_NAK);
		write_code(conn, p.ERR_NG_ALREADY_EXISTS);
		}
	write_code(conn, p.ANS_END);
} //server side

int MessageHandler::create_group(Connection *conn, string &name) 
	throw(ConnectionClosedException, ProtocolException){
	
	write_code(conn, p.COM_CREATE_NG);
	write_string(conn, name);
	write_code(conn, p.COM_END);

	char rec = read_code(conn);
	check_code("ans_create_group should be ANS_CREATE_NG started", p.ANS_CREATE_NG, rec);
	
	int ret_value = 0;
	rec = read_code(conn); //ACK or NAK?
	if(rec == p.ANS_NAK) {
		rec = read_code(conn);
		check_code("should give ERR_NG_ALREADY_EXISTS", p.ERR_NG_ALREADY_EXISTS, rec);
		ret_value = -1;
	} else {
		check_code("ans_create_ng should be ACKED", p.ANS_ACK, rec);
	}
	rec = read_code(conn);
	check_code("ans_create_ng should be ANS_END terminated", p.ANS_END, rec);
	return ret_value;
} //client side
			
void MessageHandler::ans_delete_group(Connection *conn, bool removed)
throw(ConnectionClosedException, ProtocolException){
	//COM_DELETE_NG num_p COM_END, only COM_END left.
	char rec = read_code(conn);
	check_code("delete_group should be COM_END terminated", p.COM_END, rec);
	
	//ANS_DELETE_NG [ANS_ACK | ANS_NAK ERR_NG_DOES_NOT_EXIST] ANS_END
	write_code(conn, p.ANS_DELETE_NG);
	if(removed)
		write_code(conn, p.ANS_ACK);
	else{
		write_code(conn, p.ANS_NAK);
		write_code(conn, p.ERR_NG_DOES_NOT_EXIST);
	}
	write_code(conn, p.ANS_END);
	
} //server side

bool MessageHandler::delete_group(Connection *conn, unsigned int id)
	throw(ConnectionClosedException, ProtocolException){
	write_code(conn, p.COM_DELETE_NG);
	write_uint(conn, id);
	write_code(conn, p.COM_END);
	
	char rec = read_code(conn);
	check_code("ans_delete_ng should be com ANS_DELETE_NG initialized", p.ANS_DELETE_NG, rec);
	rec = read_code(conn);
	bool removed;
	if(rec == p.ANS_ACK)
		removed = true;
	else{
		check_code("ans_delete_ng not acked, should be nacked", p.ANS_NAK, rec);
		rec = read_code(conn);
		check_code("ans_delete_ng not acked, nak should be followed by ERR_NG_DOES_NOT_EXIST", p.ERR_NG_DOES_NOT_EXIST, rec);
		removed = false;
	}
	rec = read_code(conn);
	check_code("ans_delete_group should be ANS_END terminated", p.ANS_END, rec);
	return removed;
} //client side
			
void MessageHandler::ans_list_art(Connection *conn, vector <pair<unsigned int, string> > v)
	throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	check_code("list_art should be COM_END terminated", p.COM_END, rec);

	//Shall send 	ANS_LIST_ART [ANS_ACK num_p [num_p string_p]* | ANS_NAK ERR_NG_DOES_NOT_EXIST] ANS_END
	write_code(conn, p.ANS_LIST_ART);
	if(v.size() == 1 && v[0].second == ""){ //goup non exisiting
		write_code(conn, p.ANS_NAK);
		write_code(conn, p.ERR_NG_DOES_NOT_EXIST);	
	} else {
		write_code(conn, p.ANS_ACK);
		write_uint(conn, v.size());
		for(vector <pair<unsigned int, string> >::iterator i = v.begin(); i != v.end(); i++){
			write_uint(conn, i->first);
			write_string(conn, i->second);
		}
	}
	write_code(conn, p.ANS_END);
} //server side

vector<pair<unsigned int, string> > MessageHandler::list_art (Connection *conn, unsigned int id)
	throw(ConnectionClosedException, ProtocolException){
	write_code(conn, p.COM_LIST_ART);
	write_uint(conn, id);
	write_code(conn, p.COM_END);

	vector<pair<unsigned int, string> > v;

	char rec = read_code(conn);
	check_code("ans_list_art should be ans_list_art started", p.ANS_LIST_ART, rec);

	rec = read_code(conn);
	if(rec == p.ANS_ACK){
		//rec = read_code(conn);
		unsigned int i = read_uint(conn);
		if(i != 0){
			for(int j = 0; j < i; j++){
				unsigned int n = read_uint(conn);
				string name = read_string(conn);
				v.push_back(make_pair<unsigned int, string>(n, name));
			}
		}
		
	} else {
		check_code("Expecting ANS_NAK", p.ANS_NAK, rec);
		rec = read_code(conn);
		check_code("Expecting ng_does_not_exist", p.ERR_NG_DOES_NOT_EXIST, rec);
		v.push_back(make_pair<unsigned int, string>(0, "-1"));
	}
	rec = read_code(conn);
	check_code("ans_list_art should be ans_end terminated", p.ANS_END, rec);
	return v;
} //client side
			
void MessageHandler::ans_create_art(Connection *conn, bool created){
	char rec = read_code(conn);
	check_code("create_art should be COM_END terminated", p.COM_END, rec);
	write_code(conn, p.ANS_CREATE_ART);
	if(created)
		write_code(conn, p.ANS_ACK);
	else{
		write_code(conn, p.ANS_NAK);
		write_code(conn, p.ERR_NG_DOES_NOT_EXIST);
	}
	write_code(conn, p.ANS_END);

} //server side

int MessageHandler::create_art(Connection *conn, unsigned int id,
	string title, string author, string text) throw(ConnectionClosedException, ProtocolException){
	write_code(conn, p.COM_CREATE_ART);
	write_uint(conn, id);
	write_string(conn, title);
	write_string(conn, author);
	write_string(conn, text);
	write_code(conn, p.COM_END);


	char rec = read_code(conn); //reads the servers ans. Answer depends
	check_code("Expecting ans_create_art", p.ANS_CREATE_ART, rec);

	int ret_value = 0;
	rec = read_code(conn); //ACK or NAK?
	if(rec == p.ANS_NAK) {
		rec = read_code(conn);
		check_code("should give ERR_NG_DOES_NOT_EXIST", p.ERR_NG_DOES_NOT_EXIST, rec);
		ret_value = -1;
	} else {
		check_code("ans_create_art should be ACKED", p.ANS_ACK, rec);
	}
	rec = read_code(conn);
	check_code("ans_create_art should be ANS_END terminated", p.ANS_END, rec);
	return ret_value;
} //client side
			
void MessageHandler::ans_delete_art(Connection *conn, int status)
	throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	check_code("delete_art should be COM_END terminated", p.COM_END, rec);
	write_code(conn, p.ANS_DELETE_ART);
	//ANS_DELETE_ART [ANS_ACK |
	//ANS_NAK [ERR_NG_DOES_NOT_EXIST | ERR_ART_DOES_NOT_EXIST]] ANS_END
	if(status == 0)
		write_code(conn, p.ANS_ACK);
	else{
		write_code(conn, p.ANS_NAK);
		if(status == 1)
			write_code(conn, p.ERR_NG_DOES_NOT_EXIST);
		if(status == 2)
			write_code(conn, p.ERR_ART_DOES_NOT_EXIST);
	}
	write_code(conn, p.ANS_END);


} //server side

int MessageHandler::delete_art(Connection *conn, unsigned int g_id, 
	unsigned int a_id) throw(ConnectionClosedException, ProtocolException){
	write_code(conn, p.COM_DELETE_ART);
	write_uint(conn, g_id);
	write_uint(conn, a_id);
	write_code(conn, p.COM_END);

	char rec = read_code(conn);
	check_code("ans_delete_art should be ANS_DELETE_ART initialized", p.ANS_DELETE_ART, rec);
	rec = read_code(conn);
	int result = 3;
	if(rec == p.ANS_ACK){	
		result = 0;
	} else {
		check_code("in delete_art, ans not acked so should be nacked", p.ANS_NAK, rec);
		rec = read_code(conn);
		if(rec == p.ERR_NG_DOES_NOT_EXIST)
			result = 1;
		if(rec == p.ERR_ART_DOES_NOT_EXIST)
			result = 2;
	}
	rec = read_code(conn);
	check_code("ans_delete_art should be ANS_END terminated", p.ANS_END, rec);
	return result;
	
} //client side
			
void MessageHandler::ans_get_art(Connection *conn, pair<int, map<string, string> > article)
	throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	check_code("get_art should be COM_END terminated", p.COM_END, rec);

	//ANS_GET_ART [ANS_ACK string_p string_p string_p |
	//ANS_NAK [ERR_NG_DOES_NOT_EXIST | ERR_ART_DOES_NOT_EXIST]] ANS_END
	write_code(conn, p.ANS_GET_ART);
	if(article.first == 0){
		write_code(conn, p.ANS_ACK);
		write_string(conn, article.second["title"]);
		write_string(conn, article.second["author"]);
		write_string(conn, article.second["text"]);
	} else{
		write_code(conn, p.ANS_NAK);
		if(article.first == 1)
			write_code(conn, p.ERR_NG_DOES_NOT_EXIST);
		if(article.first == 2)
			write_code(conn, p.ERR_ART_DOES_NOT_EXIST);		
	}
	write_code(conn, p.ANS_END);
	


} //server side


pair<int, map<string, string> > MessageHandler::get_art(Connection *conn, unsigned int g_id, 
	unsigned int a_id) throw(ConnectionClosedException, ProtocolException){
	//COM_GET_ART num_p num_p COM_END
	write_code(conn, p.COM_GET_ART);
	write_uint(conn, g_id);
	write_uint(conn, a_id);
	write_code(conn, p.COM_END);

	pair<int, map<string, string> > article; //int codes; 0 -> sucess, 1 -> no ng, 2-> no article
	char rec = read_code(conn);
	check_code("ans_get_art should be ANS_GET_ART initialized", p.ANS_GET_ART, rec);
	rec = read_code(conn);
	if(rec == p.ANS_ACK){
		article.second["TITLE"] = read_string(conn);
		article.second["AUTHOR"] = read_string(conn);
		article.second["TEXT"] = read_string(conn);
		article.first == 0;
	} else {
		check_code("in get_art, anser not acked so should be nacked", p.ANS_NAK, rec);
		rec = read_code(conn);
		if(rec == p.ERR_NG_DOES_NOT_EXIST)
			article.first = 1;
		else if (rec == p.ERR_ART_DOES_NOT_EXIST)
			article.first = 2;
	}
	rec = read_code(conn);
	check_code("ans_get_art should be ANS_END terminated", p.ANS_END, rec);
	return article;

	} //client side
	
	
char MessageHandler::identify(Connection *conn) throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	
	//Av någon anledning funkar inte switch med dessa enums :( 
	if(rec == p.COM_LIST_NG || rec == p.COM_CREATE_NG || rec == p.COM_DELETE_NG
		|| rec == p.COM_LIST_ART || rec == p.COM_CREATE_ART || rec == p.COM_DELETE_ART
		|| rec == p.COM_GET_ART)
		return rec;
	else {
		ProtocolException e;
		e.fcn = "Bad command from client";
		e.rec = rec;
		e.ref = 0;
		throw (e);
	}
}

void MessageHandler::write_code(Connection *conn, char i) throw(ConnectionClosedException){
		conn->write(i);
}

char MessageHandler::read_code(Connection *conn) throw(ConnectionClosedException){
	return conn->read();
}


/*
 * reads num_p, retusn unsigned int if correct
*/
unsigned int MessageHandler::read_uint(Connection *conn) throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	check_code("Recieve int parameter", p.PAR_NUM, rec);

    unsigned char byte1 = conn->read();
    unsigned char byte2 = conn->read();
    unsigned char byte3 = conn->read();
    unsigned char byte4 = conn->read();

    return (byte1 << 24) | (byte2 << 16) | 
        (byte3 << 8) | byte4;
}

/*
 * writes num_p
*/
void MessageHandler::write_uint(Connection *conn, unsigned int i) throw(ConnectionClosedException){
	write_code(conn, p.PAR_NUM);

	write_code(conn, static_cast<char>(i >> 24));
	write_code(conn, static_cast<char>(i >> 16));
	write_code(conn, static_cast<char>(i >> 8));
	write_code(conn, static_cast<char>(i));

}

/*
 * Writes string_p
*/
void MessageHandler::write_string(Connection *conn, string s) throw(ConnectionClosedException, ProtocolException){
	//string_P
	//PAR_STRING N char1 char2 char 3 ... charN
	
	write_code(conn, p.PAR_STRING);
	size_t size = s.size();
	
	write_code(conn, static_cast<char>(size >> 24)); //First byte of N
	write_code(conn, static_cast<char>(size >> 16));
	write_code(conn, static_cast<char>(size >> 8));
	write_code(conn, static_cast<char>(size)); //last byte of N
	
	for(int i = 0; i < size; i++){
		write_code(conn, s[i]); //char i
	}
}

/*
 * Reads string_p, returns a string if it is correct.
*/
string MessageHandler::read_string(Connection *conn) throw(ConnectionClosedException, ProtocolException){
	char rec = read_code(conn);
	check_code("Recieve string parameter", p.PAR_STRING, rec);
	//unsigned int length = read_uint(conn); //NO! EXPEXT par_n, not the same thing.
	unsigned char byte1 = conn->read();
    unsigned char byte2 = conn->read();
    unsigned char byte3 = conn->read();
    unsigned char byte4 = conn->read();

    unsigned int length = (byte1 << 24) | (byte2 << 16) | 
        (byte3 << 8) | byte4;
        
	string s;
	for (int i = 0; i < length; i++)
		s.append(1, static_cast<char>(conn->read()));
	return s;
}


void MessageHandler::check_code(string fcn, char ref, char rec) throw(ProtocolException){
	if(ref != rec){
		ProtocolException p;
		p.fcn = fcn;
		p.ref = ref;
		p.rec = rec;
		throw(p);
	}
}



